Skip to content

订单支付

1.1 微信支付介绍

微信支付产品

  • 付款码支付,用户打开付款码,让商家扫
  • JSAPI支付,网页端调取微信支付
  • 小程序支付,小程序中调取微信支付
  • Native支付,我们的微信生成一个码,别人扫我们支付
  • APP支付,在APP内调取微信支付
  • 刷脸支付

image-20260321132936328

1.2 微信支付准备工作

  1. 微信支付接入流程

    • 提交资料
    • 签署协议
    • 绑定场景

    image-20260321133344522

  2. 微信小程序支付

    image-20260321133513708

    • 用户请求下单: 用户在小程序选好商品,点击“支付”。

    • 商家预下单(关键): 商家系统接收请求,先在自己后台生成订单,然后调用微信支付的“统一下单接口”。

      image-20260321141008915

      mchid为商户id;out_trade_no为商户业务系统的订单号;appid为小程序appid,需要和商户id绑定;notify_url为用户支付成功后,微信通知商户的回调地址;amount有两个字段,total为支付金额,currency为支付币种;payer中的openid为微信用户id标识;

    • 微信预支付响应: 微信后台返回一个预支付交易会话标识(prepay_id)

    • 二次签名(安全核心): 商家系统拿到 prepay_id 后,不能直接给用户,必须进行再次签名(图中的步骤7),生成前端调起支付所需的参数。

    • 前端唤起支付: 用户收到商家传回的参数,小程序自动弹出微信支付密码输入框。

      image-20260321142250487

    • 双向通知(闭环):

      • 同步: 用户支付完,微信直接在界面显示结果给用户。
      • 异步(最重要): 微信后台会给商家系统发送一个回调通知(步骤13),商家根据这个通知更新订单状态(步骤14),这才算真正完成交易。
  3. 微信支付过程需要使用到微信支付平台证书、商户私钥文件。

    工具存放位置核心作用比喻
    商户私钥你的服务器对请求进行签名你的个人私章
    商户公钥微信服务器验证你的请求签名微信手里留存的你的印鉴卡
    微信平台证书你的服务器验证微信的回调签名微信给你的官方授权信
  4. 微信支付接口的回调url,需要公网ip。课程中推荐了cpolar工具,其可以给主机的某个端口映射一个临时域名,从而实现微信支付的回调通知功能,实现内网穿透。但是我已经租用了一台阿里云服务器,Gemini给我推荐了frp工具,以下是两者对比。

    维度cpolar (免费/基础版)frp (基于你的阿里云)
    带宽限制通常限制在 1-2Mbps(较慢)无限制(取决于你阿里云的带宽,通常 5Mbps+)
    域名/IP 稳定性免费版域名每 24 小时随机更换固定公网 IP(你的阿里云 IP 永远不变)
    连接数限制有并发连接限制(容易断开)无限制(取决于服务器内存,2G 足够撑起整个开发组)
    端口选择随机分配端口自定义(你可以直接映射到公网的 80 或 443 端口)
    安全性闭源软件,数据经过 cpolar 转发开源透明,数据只在你的阿里云和本地之间传输
    成本固定域名和高速带宽需按月付费完全免费(因为你已经付过云服务器的钱了)
  5. frp使用:

    • frp分为frps 和 frpc,frps运行在服务端,frpc在客户端,请求frps,frps会按照规则将请求转发到frpc。

    • 服务器 frps.toml 文件编写:

    • 本地 frpc.toml 文件编写:

      image-20260321155855902

    • 之后在服务器上开启frps服务,在本机开启frpc服务即可。可以进一步将服务器上的frps注册为系统服务

    • 可以访问服务器的7500端口可视化管理转发

    • ==之后微信支付回调我们服务器的80端口,服务器即可将流量转发给我们的Java程序==

1.3 代码开发

  1. 导入微信支付相关配置

    image-20260321164816199

  2. 用户(小程序)请求支付,商家先预下单,调用微信支付的“统一下单接口”,微信会返回一个预支付交易会话标识(prepay_id),进行再次签名,生成前端调起支付所需的参数。

    代码:

    java
        /**
         * 订单支付
         *
         * @param ordersPaymentDTO
         * @return
         */
        @PutMapping("/payment")
        @ApiOperation("订单支付")
        public Result<OrderPaymentVO> payment(@RequestBody OrdersPaymentDTO ordersPaymentDTO) throws Exception {
            log.info("订单支付:{}", ordersPaymentDTO);
            OrderPaymentVO orderPaymentVO = ordersService.payment(ordersPaymentDTO);
            log.info("生成预支付交易单:{}", orderPaymentVO);
            return Result.success(orderPaymentVO);
        }
    java
        /**
         * 订单支付
         *
         * @param ordersPaymentDTO
         * @return
         */
        public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
            // 当前登录用户id
            Long userId = BaseContext.getCurrentId();
            User user = userMapper.getById(userId);
    
            //调用微信支付接口,生成预支付交易单
            // 这里由工具类封装
            JSONObject jsonObject = weChatPayUtil.pay(
                    ordersPaymentDTO.getOrderNumber(), //商户订单号
                    new BigDecimal(0.01), //支付金额,单位 元
                    "苍穹外卖订单", //商品描述
                    user.getOpenid() //微信用户的openid
            );
    
            if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
                throw new OrderBusinessException("该订单已支付");
            }
    
            OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
            vo.setPackageStr(jsonObject.getString("package"));
    
            return vo;
        }
  3. 用户确认支付,微信支付完成后,会回调我们的Java后端服务,我们解析微信响应,确认无误后,最后更新订单状态

    代码:

    java
        /**
         * 支付成功回调
         *
         * @param request
         */
        @RequestMapping("/paySuccess")
        public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
            //读取数据
            String body = readData(request);
            log.info("支付成功回调:{}", body);
    
            //数据解密
            String plainText = decryptData(body);
            log.info("解密后的文本:{}", plainText);
    
            JSONObject jsonObject = JSON.parseObject(plainText);
            String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
            String transactionId = jsonObject.getString("transaction_id");//微信支付交易号
    
            log.info("商户平台订单号:{}", outTradeNo);
            log.info("微信支付交易号:{}", transactionId);
    
            //业务处理,修改订单状态、来单提醒
            ordersService.paySuccess(outTradeNo);
    
            //给微信响应
            responseToWeixin(response);
        }
    
        /**
         * 读取数据
         *
         * @param request
         * @return
         * @throws Exception
         */
        private String readData(HttpServletRequest request) throws Exception {
            BufferedReader reader = request.getReader();
            StringBuilder result = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        }
    
        /**
         * 数据解密
         *
         * @param body
         * @return
         * @throws Exception
         */
        private String decryptData(String body) throws Exception {
            JSONObject resultObject = JSON.parseObject(body);
            JSONObject resource = resultObject.getJSONObject("resource");
            String ciphertext = resource.getString("ciphertext");
            String nonce = resource.getString("nonce");
            String associatedData = resource.getString("associated_data");
    
            AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
            //密文解密
            String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                    nonce.getBytes(StandardCharsets.UTF_8),
                    ciphertext);
    
            return plainText;
        }
    
        /**
         * 给微信响应
         * @param response
         */
        private void responseToWeixin(HttpServletResponse response) throws Exception{
            response.setStatus(200);
            HashMap<Object, Object> map = new HashMap<>();
            map.put("code", "SUCCESS");
            map.put("message", "SUCCESS");
            response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
            response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
        }
    java
    	/**
         * 支付成功,修改订单状态
         *
         * @param outTradeNo
         */
        public void paySuccess(String outTradeNo) {
    
            // 根据订单号查询订单
            Orders ordersDB = ordersMapper.getByNumber(outTradeNo);
    
            // 根据订单id更新订单的状态、支付方式、支付状态、结账时间
            Orders orders = Orders.builder()
                    .id(ordersDB.getId())
                    .status(Orders.TO_BE_CONFIRMED)
                    .payStatus(Orders.PAID)
                    .checkoutTime(LocalDateTime.now())
                    .build();
    
            ordersMapper.update(orders);
        }